package com.zlx.mall.basic.web.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.zlx.mall.basic.web.model.RequestWrapper;
import com.zlx.mall.basic.web.model.ResponseWrapper;
import org.springframework.http.MediaType;
import org.springframework.lang.NonNull;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;

/**
 * Abstract Request Filter
 *
 * @author qingke
 * @see org.springframework.web.filter.AbstractRequestLoggingFilter
 **/
public abstract class AbstractRequestFilter extends OncePerRequestFilter {

    private static final String IGNORED_CONTENT = "ignored";

    private final ObjectMapper objectMapper;

    public AbstractRequestFilter(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    protected void doFilterInternal(@NonNull HttpServletRequest servletRequest,
                                    @NonNull HttpServletResponse servletResponse,
                                    FilterChain filterChain)
            throws ServletException, IOException {

        RequestWrapper requestWrapper = new RequestWrapper(servletRequest);
        ResponseWrapper responseWrapper = new ResponseWrapper(servletResponse);

        // before
        beforeRequest(requestWrapper);

        try {
            filterChain.doFilter(requestWrapper, responseWrapper);

        } finally {
            // after
            afterRequest(requestWrapper, responseWrapper);
            responseWrapper.copyBodyToResponse();
        }
    }

    protected boolean isJsonContentType(String contentType) {
        return StringUtils.hasText(contentType) &&
                contentType.contains(MediaType.APPLICATION_JSON_VALUE);
    }

    protected boolean isFormUrlencodedContentType(String contentType) {
        return StringUtils.hasText(contentType) &&
                contentType.contains(MediaType.APPLICATION_FORM_URLENCODED_VALUE);
    }

    protected String multiValueMapToPrettyString(MultiValueMap<String, String> map) {
        try {
            return objectMapper.writeValueAsString(map);
        } catch (Exception ignored) {
            return map.entrySet()
                    .stream()
                    .map(entry -> {
                        List<String> values = entry.getValue();
                        return "\"" + entry.getKey() + "\"" + ":" + (values.size() == 1 ?
                                "\"" + values.get(0) + "\"" : values.stream().map(s -> "\"" + s + "\"")
                                .collect(Collectors.joining(",", "[", "]")));
                    })
                    .collect(Collectors.joining(",", "{", "}"));
        }
    }

    protected String requestBodyToPrettyString(RequestWrapper wrapper) {

        if (isJsonContentType(wrapper.getContentType())) {

            try {
                // json format
                return objectMapper.readTree(wrapper.getContentAsString()).toString();

            } catch (Exception ex) {
                // ignored and out raw content
                return wrapper.getContentAsString();
            }

        } else if (isFormUrlencodedContentType(wrapper.getContentType())) {
            // form urlencoded
            return wrapper.getContentAsString();

        } else {
            return IGNORED_CONTENT;
        }
    }

    protected String responseBodyToPrettyString(ResponseWrapper wrapper) {
        return wrapper.getContentAsString();
    }

    /**
     * Before httpServletRequest
     *
     * @param requestWrapper RequestWrapper
     */
    protected abstract void beforeRequest(RequestWrapper requestWrapper);

    /**
     * After httpServletRequest
     *
     * @param requestWrapper  RequestWrapper
     * @param responseWrapper ResponseWrapper
     */
    protected abstract void afterRequest(RequestWrapper requestWrapper, ResponseWrapper responseWrapper);

}
